在進行實作之前,先來認識一下 Formik 吧~
如標題所說,Formik 是一個表單函式庫,而且還是 React 官方推薦的,相似的還有 Redux Form、React Final Form 等。並且 Formik 還可以搭配 Yup 撰寫驗證規則、訊息。
透過這些第三方的表單函式庫可以幫助我們處理 Input 的事件追蹤、驗證 Input value、透過 React Context 管理 Form 的 state及避免許多重複性值的程式碼。
現在我們要來用 React 和 Formik 建立一個表單出來。
本篇教學從 Formik 官網文件的教學部分 改寫而來
const SimpleForm = () => {
return (
<form>
<label htmlFor="name">Your Name</label>
<input type="text" id="name" />
<label htmlFor="email">Your E-Mail</label>
<input type="email" id="email" />
<button type="submit">Submit</button>
</form>
);
};
透過這個 hook 去管理表單的 state 和一些函式。
import { useFormik } from "formik";
const SimpleForm = () => {
const formik = useFormik({
initialValues: {
name: "",
email: ""
},
onSubmit: (values) => {
console.log(values);
}
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="name">Your Name</label>
<input
type="text"
id="name"
name="name"
onChange={formik.handleChange}
value={formik.values.name}
/>
<label htmlFor="email">Your E-Mail</label>
<input
type="email"
id="email"
name="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
<button type="submit">Submit</button>
</form>
);
};
上一個步驟我們是可以直接送出表單看到值,現在我們針對表單輸入內容進行驗證,因此建立一個驗證函式 validate。
將這個驗證函式加入到 useFormik 的參數物件內,會在每次觸發 onChange 和 onBlur 事件時做驗證。
在後面的步驟,會改用 Yup 做驗證
完成的表單元件如下:
import { useFormik } from "formik";
import "./styles.css";
const emailRule = /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z]+$/;
const validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = "Name must not be empty.";
} else if (values.name.length > 15) {
errors.name = "Must be 15 characters or less.";
}
if (!values.email) {
errors.email = "Email must not be empty.";
} else if (!emailRule.test(values.email)) {
errors.email = "Please enter a valid email.";
}
return errors;
};
const SimpleForm = () => {
const formik = useFormik({
initialValues: {
name: "",
email: ""
},
validate,
onSubmit: (values, { resetForm }) => {
console.log(values);
resetForm();
}
});
const showNameError = formik.touched.name && formik.errors.name;
const showEmailError = formik.touched.email && formik.errors.email;
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="name">Your Name</label>
<input
type="text"
id="name"
name="name"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.name}
className={showNameError ? "invalid" : ""}
/>
{showNameError ? (
<p className="error-text">{formik.errors.name}</p>
) : null}
<label htmlFor="email">Your E-Mail</label>
<input
type="email"
id="email"
name="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
className={showEmailError ? "invalid" : ""}
/>
{showEmailError ? (
<p className="error-text">{formik.errors.email}</p>
) : null}
<button type="submit">Submit</button>
</form>
);
};
export default SimpleForm;
雖然上個步驟的表單已經算完成了,不過我們來試試使用 Yup 吧!
將 validationSchema 和驗證規則寫在 useFormik 物件參數內即可。
import { useFormik } from "formik";
import * as Yup from "yup";
import "./styles.css";
const SimpleForm = () => {
const formik = useFormik({
initialValues: {
name: "",
email: ""
},
validationSchema: Yup.object({
name: Yup.string()
.max(15, "Must be 15 characters or less.")
.required("Name must not be empty."),
email: Yup.string()
.email("Please enter a valid email.")
.required("Email must not be empty.")
}),
onSubmit: (values, { resetForm }) => {
console.log(values);
resetForm();
}
});
const showNameError = formik.touched.name && formik.errors.name;
const showEmailError = formik.touched.email && formik.errors.email;
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="name">Your Name</label>
<input
type="text"
id="name"
name="name"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.name}
className={showNameError ? "invalid" : ""}
/>
{showNameError ? (
<p className="error-text">{formik.errors.name}</p>
) : null}
<label htmlFor="email">Your E-Mail</label>
<input
type="email"
id="email"
name="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
className={showEmailError ? "invalid" : ""}
/>
{showEmailError ? (
<p className="error-text">{formik.errors.email}</p>
) : null}
<button type="submit">Submit</button>
</form>
);
};
export default SimpleForm;
目前已經完成了表單的驗證,不過會發現到每個輸入欄都有一樣的 onChange={formik.handleChange}
、onBlur={formik.handleBlur}
等重複的程式碼,這時就可以使用 getFieldProps() 去減少這些程式碼。
// 原本的程式碼
<input
type="text"
id="name"
name="name"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.name}
className={showNameError ? "invalid" : ""}
/>
<input
type="email"
id="email"
name="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
className={showEmailError ? "invalid" : ""}
/>
// 加入 getFieldProps()
<input
type="text"
id="name"
name="name"
{...formik.getFieldProps("name")}
className={showNameError ? "invalid" : ""}
/>
<input
type="email"
id="email"
name="email"
{...formik.getFieldProps("email")}
className={showEmailError ? "invalid" : ""}
/>
在前面的實作,我們是透過 useFormik 去了解怎麼將表單加入驗證功能,而現在我們要進行改寫,會用到一個有使用到 React Context 的 Formik 的元件去取代 useFormik hook。
官網提供的關於 Formik 元件的程式碼:
import React from 'react';
import { useFormik } from 'formik';
// Create empty context
const FormikContext = React.createContext({});
// Place all of what’s returned by useFormik into context
export const Formik = ({ children, ...props }) => {
const formikStateAndHelpers = useFormik(props);
return (
<FormikContext.Provider value={formikStateAndHelpers}>
{typeof children === 'function'
? children(formikStateAndHelpers)
: children}
</FormikContext.Provider>
);
};
以下是改寫的結果,Formik 元件接受了一個函式做為它的 children:
import { Formik } from "formik";
import * as Yup from "yup";
import "./styles.css";
const SimpleForm = () => {
return (
<Formik
initialValues={{ name: "", email: "" }}
validationSchema={Yup.object({
name: Yup.string()
.max(15, "Must be 15 characters or less.")
.required("Name must not be empty."),
email: Yup.string()
.email("Please enter a valid email.")
.required("Email must not be empty.")
})}
onSubmit={(values, { resetForm }) => {
console.log(values);
resetForm();
}}
>
{(formik) => (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="name">Your Name</label>
<input
type="text"
id="name"
name="name"
{...formik.getFieldProps("name")}
className={
formik.touched.name && formik.errors.name ? "invalid" : ""
}
/>
{formik.touched.name && formik.errors.name ? (
<p className="error-text">{formik.errors.name}</p>
) : null}
<label htmlFor="email">Your E-Mail</label>
<input
type="email"
id="email"
name="email"
{...formik.getFieldProps("email")}
className={
formik.touched.email && formik.errors.email ? "invalid" : ""
}
/>
{formik.touched.email && formik.errors.email ? (
<p className="error-text">{formik.errors.email}</p>
) : null}
<button type="submit">Submit</button>
</form>
)}
</Formik>
);
};
export default SimpleForm;
注意把 useFormik 的參數物件移到 Formik 元件內時要改成 JSX 語法
最後一個步驟,加入一些 formik 提供的元件,讓程式碼再次變更精簡!
import { Formik, Field, Form, ErrorMessage } from "formik";
import * as Yup from "yup";
import "./styles.css";
const SimpleForm = () => {
return (
<Formik
initialValues={{ name: "", email: "" }}
validationSchema={Yup.object({
name: Yup.string()
.max(15, "Must be 15 characters or less.")
.required("Name must not be empty."),
email: Yup.string()
.email("Please enter a valid email.")
.required("Email must not be empty.")
})}
onSubmit={(values, { resetForm }) => {
console.log(values);
resetForm();
}}
>
{(formik) => (
<Form onSubmit={formik.handleSubmit}>
<label htmlFor="name">Your Name</label>
<Field
name="name"
render={({ field, meta }) => (
<input
type="text" {...field}
className={meta.error ? "invalid" : ""}
/>
)}
/>
<ErrorMessage name="name">
{(err) => <p className="error-text">{err}</p>}
</ErrorMessage>
<label htmlFor="email">Your E-Mail</label>
<Field
name="email"
render={({ field, meta }) => (
<input
type="text" {...field}
className={meta.error ? "invalid" : ""}
/>
)}
/>
<ErrorMessage name="email">
{(err) => <p className="error-text">{err}</p>}
</ErrorMessage>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
);
};
export default SimpleForm;
這樣就完成了所有的實作,底下附上放在 codesandbox 上的程式碼。